`
https://leetcode.cn/problems/count-complete-substrings/
`

/**
 * @param {string} word
 * @param {number} k
 * @return {number}
 */
var countCompleteSubstrings = function (word, k) {
  // 相邻字母相差至多为 2 的约束将 word 分为多个子串
  // 使用分组循环找到每个字串，分别处理即可
  const n = word.length

  // 处理字符串，传入字符串满足约束：相邻字母相差至多为 2
  // 返回有多少个子串满足每个字符恰好出现 k 次
  const f = (s) => {
    const len = s.length
    let res = 0
    for (let m = 1; m <= 26; m++) {
      if (s.length < k * m) break
      // 统计每个字符的出现次数
      const { get, set } = counter()
      // 统计出现次数为 k 的字母个数
      let cur = 0
      // 维护一个长度为 k * m 的滑动窗口，寻找 m 个字母出现 k 次的子串
      let left = 0, right = 0
      while (right < len) {
        const c = s[right++]
        if (get(c) === k) cur--
        set(c, get(c) + 1)
        if (get(c) === k) cur++

        if (right - left >= k * m) {
          if (cur === m) res++
          const d = s[left++]
          if (get(d) === k) cur--
          set(d, get(d) - 1)
          if (get(d) === k) cur++
        }
      }
    }
    return res
  }

  let res = 0, i = 0
  // 分组循环，拿到每一个子串
  while (i < n) {
    const start = i
    // 从第二个字符开始找
    i++
    while (i < n && Math.abs(word.charCodeAt(i) - word.charCodeAt(i - 1)) <= 2) {
      i++
    }
    // 此时 [start, i - 1] 是一个分组
    res += f(word.substring(start, i))
  }

  return res
};

function counter() {
  const cnt = new Array(26).fill(0)
  const ordA = 'a'.charCodeAt(0)
  return {
    get(c) {
      return cnt[c.charCodeAt(0) - ordA]
    },
    set(c, val) {
      cnt[c.charCodeAt(0) - ordA] = val
    }
  }
}